创建时间: | 2019/1/25 9:34 |
来源: | https://www.jianshu.com/p/4ca05e4651b2 |
今天帮群里一个伙伴解决Activiti的一个问题,花了点功夫,特此记录下来。
在用Activiti做会签功能的时候,我们经常是使用 User Task的多实例。
如下图所示,一个简单多任务实例的配置:
我们必须要设置两个非常重要的变量: Collection, Element variable。 至于他们为何重要,了解会签的人都应该知道,我之所以认为他们重要,还有两个重要的原因:
总结一下这里想要实现的功能(这里不对会签的基本用法进行说明,假定你对会签很了解,特别是对Activiti中配置会签非常了解):
测试中用到的流程定义文件(流程图如文章刚开始的示例图):
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
<process id="meeting_audit_process" name="Meeting Audit Process" isExecutable="true">
<startEvent id="startEvent1"></startEvent>
<userTask id="meetingAudit" name="Meeting Audit" activiti:async="false" activiti:exclusive="false" activiti:assignee="13500000001,13500000002" activiti:candidateGroups="test_group">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${assigneeList}" activiti:elementVariable="assignee">
<completionCondition>${nrOfCompletedInstances >= 1}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-9D4EDD47-E993-46FB-9738-8D65BDAF2C75" sourceRef="meetingAudit" targetRef="sid-1B2B1A10-BB81-4C57-8EB8-65883A8859DC"></sequenceFlow>
<endEvent id="sid-1B2B1A10-BB81-4C57-8EB8-65883A8859DC">
<terminateEventDefinition></terminateEventDefinition>
</endEvent>
<sequenceFlow id="sid-F8F8CFB1-DD3A-46D4-B7A1-917D6DA04AD6" sourceRef="startEvent1" targetRef="meetingAudit"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_meeting_audit_process">
<bpmndi:BPMNPlane bpmnElement="meeting_audit_process" id="BPMNPlane_meeting_audit_process">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="315.0" y="345.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="meetingAudit" id="BPMNShape_meetingAudit">
<omgdc:Bounds height="80.0" width="100.0" x="450.0" y="330.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-1B2B1A10-BB81-4C57-8EB8-65883A8859DC" id="BPMNShape_sid-1B2B1A10-BB81-4C57-8EB8-65883A8859DC">
<omgdc:Bounds height="28.0" width="28.0" x="690.0" y="360.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-F8F8CFB1-DD3A-46D4-B7A1-917D6DA04AD6" id="BPMNEdge_sid-F8F8CFB1-DD3A-46D4-B7A1-917D6DA04AD6">
<omgdi:waypoint x="345.0" y="360.0"></omgdi:waypoint>
<omgdi:waypoint x="397.5" y="360.0"></omgdi:waypoint>
<omgdi:waypoint x="397.5" y="370.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="370.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-9D4EDD47-E993-46FB-9738-8D65BDAF2C75" id="BPMNEdge_sid-9D4EDD47-E993-46FB-9738-8D65BDAF2C75">
<omgdi:waypoint x="550.0" y="370.0"></omgdi:waypoint>
<omgdi:waypoint x="620.0" y="370.0"></omgdi:waypoint>
<omgdi:waypoint x="620.0" y="374.0"></omgdi:waypoint>
<omgdi:waypoint x="690.0" y="374.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
实现 #1 需要的主要代码:
// 根据需要启动的流程实例来获得相关的流程定义
ProcessDefinition meetingAuditProcessDefinition = listPDs.get(0);
BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(meetingAuditProcessDefinition.getId());
Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
for(FlowElement e : flowElements) {
if (e instanceof UserTask) {
UserTask userTask = (UserTask) e;
Object behavior = userTask.getBehavior();
if (behavior instanceof MultiInstanceActivityBehavior) { // 多实例用户任务
// 可以根据获取的候选组/或者指派人员ID列表查询用户列表,设定到多实例的变量
List<String> assigneeList = new ArrayList<>();
String assignees = userTask.getAssignee();
for (String assigneeId : assignees.split(",")) {
if (StringUtils.isNotBlank(assigneeId)) {
assigneeList.add(assigneeId);
}
}
processVars.put("assigneeList", assigneeList);
}
}
}
实现 #2 需要在流程定义中通过程序的方式加入自己的executionListener, 由于这里只关注会签任务,就只需要在流程定义中对会签任务节点加上监听器。这里实现的方式是用postBpmnParseHandlers, 这个看起来是在流程引擎对流程定义进行解析,准备创建下一个Activiti时出发的。我就是在这里把executionListener注入到任务(此时只是创建Activity, 而不是多实例的user task)。
请注意下面的实现都是基于Activiti 6.0.0, 版本5.x.x 如此功能的代码相差非常大
import org.activiti.bpmn.model.ActivitiListener;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.bpmn.parser.handler.UserTaskParseHandler;
/**
* @Description:
* @Author: Yong Li
* @Date: 2018/8/29 16:56
*/
public class MultipleUserTaskBpmnParseHandler extends UserTaskParseHandler {
private final String eventName;
private final TaskListener taskListener;
public MultipleUserTaskBpmnParseHandler() {
this.eventName =TaskListener.EVENTNAME_CREATE; // 监听任务创建事件
this.taskListener = new MultipleTaskListener();
}
public MultipleUserTaskBpmnParseHandler(String eventName, TaskListener taskListener) {
this.eventName = eventName;
this.taskListener = taskListener;
}
@Override
protected void executeParse(BpmnParse bpmnParse, UserTask userTask) {
super.executeParse(bpmnParse, userTask);
if (userTask.getLoopCharacteristics() instanceof MultiInstanceLoopCharacteristics) {
ActivitiListener listener = new ActivitiListener();
listener.setEvent(eventName);
//listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_INSTANCE);
//listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_INSTANCE);
//listener.setInstance(taskListener);
listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
listener.setImplementation("#{multipleTaskListener}");
userTask.getTaskListeners().add(listener);
}
}
}
需要在processEngineConfiguration把这个监听器注册进去
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
...
<property name="postBpmnParseHandlers">
<list>
<bean class="workflow.listener.MultipleUserTaskBpmnParseHandler"/>
</list>
</property>
</bean>
剩下就是在executionListener中对任务创建进行监听并进行任务指派了:
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
public class MultipleTaskListener implements TaskListener {
@Resource
private ProcessEngine processEngine;
// 流程实例ID -> 已经分配的人员列表 (如果一个流程实例里面有多个多任务实例的情况,需要重新定义map的key)
private Map<String, List<String>> processInstanceTaskIdsMap = new HashMap<>();
@Override
public void notify(DelegateTask delegateTask) {
String processInstanceId = delegateTask.getProcessInstanceId();
if (!processInstanceTaskIdsMap.containsKey(processInstanceId)) {
processInstanceTaskIdsMap.put(processInstanceId, new ArrayList<>());
}
processInstanceTaskIdsMap.get(processInstanceId).add(delegateTask.getId());
// 最后一个多实例任务通知时,把所有任务进行指派
String[] allAssignees = delegateTask.getAssignee().split(",");
if (processInstanceTaskIdsMap.get(processInstanceId).size() == allAssignees.length) {
List<String> taskIds = processInstanceTaskIdsMap.get(processInstanceId);
for (int index = 0; index < taskIds.size(); index++) {
processEngine.getTaskService().setAssignee(taskIds.get(index), allAssignees[index]);
}
}
}
public ProcessEngine getProcessEngine() {
return processEngine;
}
public void setProcessEngine(ProcessEngine processEngine) {
this.processEngine = processEngine;
}
}
由于在指定监听器的时候用的是表达式,那么这里的监听器实例可以注册在spring容器中:
<bean id="multipleTaskListener" class="workflow.listener.MultipleTaskListener" depends-on="processEngine">
<property name="processEngine" ref="processEngine" />
</bean>
如下图所示,再进行任务查找的时候,任务的assignee已经变成了单个用户了,之前在流程设计时定义流程任务的assignee为“13500000001,13500000002”。